## Copyright (C) 2010, Scott W. Dunlop <swdunlop@gmail.com>
## All rights reserved.
## 
## Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are met:
##    * Redistributions of source code must retain the above copyright
##      notice, this list of conditions and the following disclaimer.
##    * Redistributions in binary form must reproduce the above copyright
##      notice, this list of conditions and the following disclaimer in the
##      documentation and/or other materials provided with the distribution.
##    * Neither the name of the <organization> nor the
##      names of its contributors may be used to endorse or promote products
##      derived from this software without specific prior written permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
## ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
## DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
## (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
## LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""
This module wraps nxpy, a low level C wrapper around the NoSpex graph library;
it provides a "pythonic" interface, full of wonderful things like properties,
event handlers, abstract classes and other garbage that makes GoF pattern nuts
happy.
"""

import nxpy

FROM_SRC = nxpy.FROM_SRC
FROM_DST = nxpy.FROM_DST

__all__ = [
    "NoSpexGraph",
    "NoSpexInfo",
    "NoSpexNode",
    "NoSpexBlip",
    "NoSpexLink",
    "FROM_SRC",
    "FROM_DST",
    "control"
]

class NoSpexError( Exception ):
    pass

class NoSpexObject( object ):
    #TODO: __hash__ method.
    #TODO: __cmp__ method.

    def __init__( self, handle, graph = None ):
        #TODO: Throw an exception if handle is not a NoSpexResource.
        #TODO: Seal handle so it is write-once.
        self.handle = handle
        self.graph = graph
        #TODO: Throw an exception if there is already a wrapper.
        if graph is not None:
            graph.wrappers[handle] = self
    
    def __repr__( self ):
        return repr( self.handle )

    def isElement( self ):
        return nxpy.is_element( self.handle )

    def isInfo( self ):
        return nxpy.is_info( self.handle )

    def isGraph( self ): 
        return nxpy.is_graph( self.handle )

    def isLink( self ): 
        return nxpy.is_link( self.handle )

    def isNode( self ):
        return nxpy.is_node( self.handle )

    def isBlip( self ):
        return nxpy.is_blip( self.handle )

    def handleClick( self ):
        pass

    def handleEnter( self ):
        pass

    def handleLeave( self ): 
        pass

    def handleUnlink( self ):
        del self.graph.wrappers[self.handle]

    def unlink( self ):
        nxpy.unlink_resource( self.handle )

    @property 
    def color( self ):
        return nxpy.resource_color( self.handle )

    @color.setter
    def color( self, value ):
        if isinstance( value, tuple ) or isinstance( value, list ):
            nxpy.set_resource_color( self.handle, value[0], value[1], value[2] )
        else:
            raise NxpyError( "Expected a tuple of R, G, B components." )
        self.graph.requestRender( )

class NoSpexInfo( NoSpexObject ):
    def __init__( self, element, text ): 
        handle = nxpy.create_info( element.handle, text )
        NoSpexObject.__init__( self, handle, element.graph )
        element.info = self

    def unlink( self ):
        if self.graph is not None:
            self.graph.requestRender( )
        NoSpexObject.unlink( self )

class NoSpexElement( NoSpexObject ):
    """
    Abstract object that provides common behaviors for Elements.
    """
    def __init__( self, handle, graph = None ):
        NoSpexObject.__init__( self, handle, graph ) 
        self._info = None
    
    #TODO: Use nxpy.element_info instead of this _info cache.
    @property
    def info( self ):
        return self._info
    
    @info.setter
    def info( self, value ):
        oldInfo = self._info
        if oldInfo is not None:
            oldInfo.unlink( )

        if isinstance( value, str ):
            self._info = None
            NoSpexInfo( self, value )
        else:
            self._info = value
            self.graph.requestRender( )
        
        return self
    
    @info.deleter
    def info( self ):
        if self._info is not None:
            self._info.unlink( )
        self._info = None

    @property 
    def position( self ):
        return nxpy.element_position( self.handle )
    
    def unlink( self ):
        if self.graph is not None:
            self.graph.requestRender( )
        NoSpexObject.unlink( self )

class NoSpexNode( NoSpexElement ):
    def __init__( self, graph ):
        handle = nxpy.create_node( graph.handle )
        NoSpexElement.__init__( self, handle, graph ) 
        graph.requestPhysics( )

    @property 
    def position( self ):
        return nxpy.element_position( self.handle )

    @position.setter
    def position( self, value ):
        if isinstance( value, tuple ) or isinstance( value, list ):
            nxpy.set_node_position( self.handle, value[0], value[1] )
        else:
            raise NxpyError( "Expected a pair of X, Y coordinates." )
        self.graph.requestPhysics( )
        self.graph.requestRender( )

    def unlink( self ):
        NoSpexElement.unlink( self )
        self.graph.requestPhysics( )

class NoSpexLink( NoSpexElement ):
    def __init__( self, source, destination ):
        graph = source.graph
        handle = nxpy.create_link( source.handle, destination.handle )
        NoSpexElement.__init__( self, handle, graph ) 
        self.source = source
        self.destination = destination
        graph.requestPhysics( )
    
    def unlink( self ):
        NoSpexElement.unlink( self )
        self.graph.requestPhysics( )

class NoSpexBlip( NoSpexElement ):
    #TODO: position property, read-only

    def __init__( self, link, duration, direction=FROM_SRC ): 
        graph = link.graph
        handle = nxpy.create_blip( link.handle, direction, duration )

        NoSpexElement.__init__( self, handle, graph )
        self.link = link
        graph.requestBlips( )

class NoSpexGraph( NoSpexObject ):
    """
    You may expand how NoSpex handles are mapped / wrapped by overriding the
    wrapObject methods below.
    """
    
    #widgetWrapper = None
    
    def __init__( self ): 
        handle = nxpy.create_graph( )
        self.wrappers = { handle : self }
        NoSpexObject.__init__( self, handle )
        nxpy.set_graph_callback( handle, self.handleEvent )
    
    @property
    def links( self ):
        for object in self.wrappers.itervalues():
            if object.isLink( ):
                yield object

    @property
    def nodes( self ):
        for object in self.wrappers.itervalues():
            if object.isNode( ):
                yield object

    @property
    def blips( self ):
        for object in self.wrappers.itervalues():
            if object.isBlip( ):
                yield object

    #def wrapWidget( self, widget ):
    #   if self.widgetWrapper is None:
    #       return widget # We do not currently track or wrap widgets.
    #   else:
    #       return self.widgetWrapper( widget, self )
    #
    #def createWidget( self ): 
    #    return self.wrapWidget( nxpy.create_widget( self.handle ) )
    
    def display( self ):
        return nxpy.display_graph( self.handle )

    def handleEvent( self, rsrc, id, x, y ):
        wrapper = self.wrappers.get( rsrc )
        if wrapper is None: 
            print self.wrappers
            raise NoSpexError( 
                "Unregistered handle associated with an event: %r" % rsrc
            )

        if id == nxpy.ELEMENT_CLICK_EVT:
            wrapper.handleClick( )
        elif id == nxpy.ELEMENT_ENTER_EVT:
            wrapper.handleEnter( )
        elif id == nxpy.ELEMENT_LEAVE_EVT:
            wrapper.handleLeave( )
        elif id == nxpy.ELEMENT_UNLINK_EVT:
            wrapper.handleUnlink( )
        elif id == nxpy.GRAPH_CLICK_EVT:
            wrapper.handleClick( )
        elif id == nxpy.GRAPH_UNLINK_EVT:
            wrapper.handleUnlink( )
        else:
            print "Unhandled id %s for graph %s." % (id,self)
            return # WTF is that id?
    
    def handleUnlink( self ):
        self.wrappers = {}

    def suspend( self ):
        pass #TODO

    def resume( self ):
        pass #TODO

    def requestPhysics( self ): 
        nxpy.request_graph_physics( self.handle )

    def requestRender( self ):
        nxpy.request_graph_render( self.handle )

    def requestBlips( self ):
        nxpy.request_graph_blips( self.handle )

class NoSpexControl:
    def __enter__( self ):
        nxpy.seize_control( )

    def __exit__( self, type, value, traceback ):
        nxpy.release_control( )
        return False

control = NoSpexControl( )

